﻿package com.frontalcode
{
	import com.frontalcode.*;
	import flash.display.*;
	import flash.utils.*;
	
	/**                  
     * The DocumentManager class provides a central integration point for all the
	 * current Frontal documents. It is singleton class meaning there is only ever one
	 * instance of it. This instance is available from this class's getInstance static
	 * method or from any DocumentElement instance via its mgr property.
     */
	public class DocumentManager extends OptionableObject
	{
		static private var _instance : DocumentManager;
		
		/**            
		 * The top-level movie from which things like URL parameters and frame rates may be
		 * queried.
		 */
		public var loaderMovie : DisplayObjectContainer;
		
		/**            
		 * The default parent for rendered Frontal documents.
		 */
		public var movie : DisplayObjectContainer;
		
		/**            
		 * An Object for scripts to use to store instance-specific data.
		 */
		public var dynamic : Object = { };
		
		/**            
		 * An Object for scripts to use to store instance-specific data.
		 */
		private var manualLoaderParams : Object = { };
		
		/**            
		 * @private
		 */
		internal var documents : Array = [ ];
		        
		/**
		 * Creates a new DocumentManager instance.  
		 *
		 * <p>Options may be set through the constructor, the movie's URL or the
		 * movie's FlashVars parameter. The following options are supported:
		 * 	<table class="innertable">
		 * 		<tr><th>Option</th><th>Default</th><th>Description</th></tr>
		 * 		<tr><td>defaultDocumentDefnURL</td><td>site.xml</td><td>The default Frontal document.</td></tr>
		 * 	</table>
		 * </p>  
		 *
		 * @param movie			The default parent for rendered Frontal documents.
		 * @param loaderMovie	The top-level movie from which things like URL parameters and frame rates may be
		 * 						queried. If the same as movie then this may be left null.                     
		 * @param options		Options affecting the behavior of this instance. 
		 *
		 * @throws com.frontalcode.FrontalError Only one instance of this class may be created.
		 */  
		public function DocumentManager ( movie : DisplayObjectContainer, loaderMovie : DisplayObjectContainer = null, options : Object = null )
		{
			super ( options );  
			
			if ( _instance != null ) throw new FrontalError ( "Document manager already initialized" );
			
			this.loaderMovie = loaderMovie;
			this.movie = movie;
			
			_instance = this;
			
			setObjectOptionDefault ( "isStyleInliner", false );
			setObjectOptionDefault ( "defaultDocumentDefnURL", "frontal_document.xml" );
			setObjectOptionDefault ( "defaultDocumentClass", "com.frontalcode.DocumentComplete" );
			setObjectOptionDefault ( "profile", false );
			setObjectOptionDefault ( "language", null );
			setObjectOptionDefault ( "htmlElemId", "movie" );
			setOptionsFromLoaderParameters ( );
			
			// Initialize the scheduler.
			//
			new Scheduler ( );
			
			// Initialize the debugger.
			//
			new Debugger ( null );
			
			// Initialize the asset manager.
			//
			new AssetManager ( );
			
			// Initialize the deep link manager.
			//
			new DeepLinkManager ( );
		}
		
		/**
		 * The single instance of the DocumentManager class.
		 *
		 * @return The document manager.
		 */
		static public function getInstance ( ) : DocumentManager
		{
			if ( _instance == null ) throw new FrontalError ( "Document manager not initialized" );
			return _instance;
		}
		
		/**
		 * The single instance of the DocumentManager class. Equivalent to getInstance().
		 *
		 * @return The document manager.
		 */
		static public function gI ( ) : DocumentManager { return getInstance ( ); }
		
		/**
		 * The version of the Frontal Renderer.
		 *
		 * @return The document manager.
		 */
		static public function get version ( ) : String 
		{
			// Our version number contains 3 sequences:
			//
			// 1. Major version
			// 2. Minor version
			// 3. Patch number
			//
			// TODO: Need instructions for adding a tag to SVN and then getting
			// it keyword-replaced here. For now, it's hardcoded.
			//
			// Version history:
			//
			// 1.0		Various releases made before implementing this versioning
			//			system.
			// 1.0.1	10/20/09 A patch made for a text rendering issue. 
			// 1.1		10/21/09 Latest build. 
			// 1.2		10/23/09 Latest build. 
			// 1.3		10/30/09 Merge performance2 branch with rendering and 
			//			         style sheet optimizations. 
			// 1.4		01/21/10 Latest build. 
			// 1.4.1	01/26/10 Video deep link issue fixed. 
			// 1.4.2	02/02/10 Some fixes for Twitter integration. 
			// 1.5		03/04/10 Latest build. 
			// 1.6		04/30/10 First open source build. Removed license checks. 
			// 1.7		06/16/10 Some layout bugs and small enhancements. 
			// 1.8		06/22/10 Fix layout bugs missed in testing 1.7. 
			// 1.8.1	06/23/10 Development release with fix for issue #4.
			// 1.9		Compile for Flash 10.
			// 1.10		Various ActionScriptInterpreter changes mainly.
			//
			return "1.10";
		}
		
		/**
		 * Returns the movie's frame rate in frames per second. This is taken
		 * from the result of getLoaderInfo().
		 *
		 * @return The frame rate.
		 *
		 * @see #getLoaderInfo()
		 */
		public function get fps ( ) : Number
		{
			var frameRate : Number = 30;
			
			try
			{
				frameRate = getLoaderInfo ( ).frameRate;
			}
			catch ( e : Error )
			{
				// Likely "Error #2099: The loading object is not sufficiently loaded to provide this information."
			}
			
			return frameRate;
		}
		
		/**
		 * Returns the flash.display.LoaderInfo for the movie. If a loaderMovie
		 * was specified in the constructor then the LoaderInfo is for it.
		 *
		 * @return The LoaderInfo instance.
		 */
		public function getLoaderInfo ( ) : LoaderInfo
		{
			return loaderMovie != null ? loaderMovie.root.loaderInfo : movie.root.loaderInfo;
		}
		
		/**
		 * Returns any value associated with the given parameter in the movie's
		 * FlashVars or URL.
		 *
		 * @param param		The name of the parameter. 
		 *
		 * @return The value associated with the parameter or undefined.
		 */
		public function getLoaderParameter ( param : String ) : *
		{
			var value : * = undefined;
			
			if ( manualLoaderParams.hasOwnProperty ( param ) ) return manualLoaderParams [ param ];
			
			try
			{
				if ( movie.root.loaderInfo.parameters.hasOwnProperty ( param ) ) value = movie.root.loaderInfo.parameters [ param ];
				
				if ( loaderMovie != null && value === undefined && loaderMovie.root.loaderInfo.parameters.hasOwnProperty ( param ) )
				{
					value = loaderMovie.root.loaderInfo.parameters [ param ];
				}
			}
			catch ( error : * )
			{
				// Probably running the StyleInliner.
				//
			}
			
			return value;
		}
		
		public function setLoaderParameter ( param : String, value : *  ) : void
		{
			manualLoaderParams [ param ] = value;
		}
		
		/**
		 * Creates an empty Frontal document and add it to this manager.
		 *
		 * @param movie		The parent movie for the rendered document. If null then the movie passed to the 
		 *                  constructor is used.
		 * @param title		The title for the document. This can be used for accessing the document. The
		 *                  default is "untitled".
		 * @param options	Options for the document.
		 * 
		 * @return The new Document instance.
		 */
		public function addDocument ( movie : DisplayObjectContainer = null, title : String = "untitled", options : Object = null ) : Document
		{
			if ( movie == null ) movie = this.movie;
			var documentClass : Class;
			if ( options != null && options.hasOwnProperty ( "documentClass" ) )
			{
				var value : * = options.documentClass;
				if ( ! ( value is Class ) ) value = getDefinitionByName ( value );
				documentClass = value as Class;
			}
			if ( documentClass == null ) documentClass = getDefinitionByName ( getObjectOption ( "defaultDocumentClass" ) ) as Class;
			var document : Document = new documentClass ( movie, title, options );
			documents.push ( document );
			return document;
		}
		
		/**
		 * Creates an empty Frontal document, adds it to this manager and loads 
		 * the default Frontal document file.
		 *
		 * @param movie		The parent movie for the rendered document. If null then the movie passed to the 
		 *                  constructor is used.
		 * @param title		The title for the document. This can be used for accessing the document. The
		 *                  default is "untitled".
		 * @param options	Options for the document.
		 * 
		 * @return The new Document instance.
		 */
		public function addDefaultDocument ( movie : DisplayObjectContainer = null, title : String = "default", options : Object = null ) : Document
		{
			var document : Document = addDocument ( movie, title, options );
			document.load ( getObjectOption ( "defaultDocumentDefnURL" ) as String );
			return document;
		}
		
		/**
		 * Creates an empty Frontal document, adds it to this manager and loads 
		 * the specified Frontal document file in it.
		 *
		 * @param url		The url of the Frontal document file.
		 * 
		 * @return The new Document instance.
		 */
		public function open ( url : String ) : Document
		{
			if ( url == null || Util.isEmpty.test ( url ) ) return null;
			var document : Document = DocumentManager.getInstance ( ).addDocument ( );
			document.load ( url ); 
			return document;
		}
		
		/**
		 * Remove and destroy the Frontal document.
		 *
		 * @param document		The document to remove.
		 */
		public function removeDocument ( document : Document ) : void
		{
			// XXX Do we need to remove this document's assets and deep links?
			//
			
			var i : int = documents.indexOf ( document );
			if ( i >= 0 ) documents.splice ( i, 1 );
			document.destroy ( );
		}
		
		/**
		 * Find an existent document.
		 *
		 * @param index		The index of the document. Either a 1-based numeric index or the title of the
		 *                  document.
		 *
		 * @return The document or null.
		 */
		public function getDocument ( index : * ) : Document
		{
			var result : Document = null;
			
			if ( ! isNaN ( index ) && Number ( index ) > 0 && Number ( index ) <= documents.length ) 
			{
				result = documents [ Number ( index ) - 1 ];
			}
			else
			{
				for ( var i : uint = 0; i < documents.length; i++ )
				{
					if ( documents [ i ].title == index )
					{
						result = documents [ i ];
						break;
					}
				}
			}
		
			return result;
		}
		
		/**
		 * Find an existent document. Equivalent to getDocument().
		 *
		 * @return The document or null.
		 */
		public function gD ( index : * ) : Document { return getDocument ( index ); }
	}
}